﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

using Alicanto.Helpers;

namespace Alicanto.Compilation.Code.Blocks.Iteration
{
    internal class Foreach : ICodeGenerator
    {
        public Expression Code { get; private set; }

        public IEnumerable<ParseInstruction> Generate(TokenSequence tokens, Scope scope)
        {
            ParseExpression enumerable = new ParseExpression();
            yield return enumerable;

            Expression x = enumerable.Expression;
            x = Boxing.EnsureUnbox(x, typeof(IEnumerable));

            // Find enumeration type
            Type typeEnumerable = x.Type;
            Type typeEnumerator = null;

            if (typeEnumerable.GetMethod("GetEnumerator") != null)
            {
                typeEnumerator = typeEnumerable.GetMethod("GetEnumerator").ReturnType;
            }
            else
            {
                typeEnumerable = typeEnumerable.GetInterface(typeof(IEnumerable<>).Name);
                if (typeEnumerable == null)
                {
                    typeEnumerable = typeEnumerable.GetInterface(typeof(IEnumerable).Name);
                }

                typeEnumerator = typeEnumerable.GetMethod("GetEnumerator").ReturnType;
            }

            Type typeCurrent = typeEnumerator.GetProperty("Current").PropertyType;

            ParameterExpression index = Expression.Parameter(typeof(int));
            ParameterExpression current = Expression.Parameter(typeCurrent);
            Parse body = new Parse();

            EnterScope enter = new EnterScope();

            yield return enter;
            enter.Scope["__current__"] = current;
            enter.Scope["__index__"] = index;


            yield return body;
            yield return new LeaveScope();

            ParameterExpression enumerator = Expression.Parameter(typeEnumerator);

            Expression assignEnumerator = Expression.Assign(
                enumerator,
                Expression.Call(
                    x,
                    typeEnumerable.GetMethod("GetEnumerator")
                )
            );

            Expression assignCurrent = Expression.Assign(
                current,
                Expression.Property(
                    enumerator,
                    "Current"
                )
            );

            Expression assignIndex = Expression.Assign(
                index,
                Expression.Constant(-1)
            );

            Expression moveNext = Expression.Block(
                Expression.Assign(
                    index,
                    Expression.Increment(index)
                ),
                Expression.Call(
                    enumerator,
                    typeof(IEnumerator).GetMethod("MoveNext")
                )
            );

            LabelTarget @break = Expression.Label();

            List<ParameterExpression> variables = new List<ParameterExpression>();
            variables.AddRange(enter.Scope.Declarations);
            variables.Add(enumerator);
            variables.Add(index);
            variables.Add(current);

            this.Code = Expression.Block(
                variables,
                assignEnumerator,
                assignIndex,
                Expression.Loop(
                    Expression.Block(
                        Expression.IfThenElse(
                            moveNext,
                            Expression.Block(
                                assignCurrent,
                                Expression.Block(
                                    body.Expressions
                                )
                            ),
                            Expression.Break(@break)
                        )
                    )
                ),
                Expression.Label(@break)
            );

        }
    }
}
